#include "GLEffect.h"
#include <Rz/Diagnostics/Logger.h>
#include <Rz/Graphics/Render/IRenderDevice.h>
#include <Rz/IO/File.h>
#include "GL.h"
#include "GLEffectProperty.h"

#ifdef _DEBUG
#	define RZ_GL_PRINTSTATUS(obj, type)	Debug(obj, type)
#else
#	define RZ_GL_PRINTSTATUS(obj, type)
#endif

namespace Rz { namespace Graphics { namespace Render { namespace Libraries { namespace OpenGL {

GLEffect::GLEffect(IRenderDevice* renderDevice)
	//: GLEffect(renderDevice, nullptr, nullptr)
	: _vertShader(nullptr)
	, _pixShader(nullptr)
	, _vertShaderHandle(0)
	, _pixShaderHandle(0)
{
	this->SetRenderDevice(renderDevice);
}

GLEffect::GLEffect(IRenderDevice* renderDevice, const String& shader)
	: _vertShaderHandle(0)
	, _pixShaderHandle(0)
{
	this->SetRenderDevice(renderDevice);
	this->Init(shader);
}

GLEffect::~GLEffect()
{
	if (!_programHandle)
	{
		GL::DeleteProgram(_programHandle);
	}

	if (!_vertShaderHandle)
	{
		GL::DeleteShader(_vertShaderHandle);
	}

	if (!_pixShaderHandle)
	{
		GL::DeleteShader(_pixShaderHandle);
	}
}

void GLEffect::Bind() 
{
	GL::UseProgram(_programHandle);

	this->ResetUnitCounter();

	for (auto& e : this->GetPropertyCollection())
	{
		e->Apply();
	}
}

void GLEffect::Unbind() 
{
	GL::UseProgram(0);
}

void GLEffect::Init(const String& shader) 
{
	_vertShader = IO::File::ReadAllText("rz/graphics/shaders/opengl/" + shader + "/" + shader + ".vp");
	_pixShader  = IO::File::ReadAllText("rz/graphics/shaders/opengl/" + shader + "/" + shader + ".fp");

	const GLchar* v = static_cast<const GLchar*>(_vertShader.GetCharArray());
	const GLchar* f = static_cast<const GLchar*>(_pixShader.GetCharArray());

	// compile vertex shader.
	_vertShaderHandle = GL::CreateShader(GL_VERTEX_SHADER);
	GL::ShaderSource(_vertShaderHandle, 1, &v, NULL);
	GL::CompileShader(_vertShaderHandle);
	RZ_GL_PRINTSTATUS(_vertShaderHandle, "vertex shader");

	// compile fragment shader.
	_pixShaderHandle = GL::CreateShader(GL_FRAGMENT_SHADER);
	GL::ShaderSource(_pixShaderHandle, 1, &f, NULL);
	GL::CompileShader(_pixShaderHandle);
	RZ_GL_PRINTSTATUS(_pixShaderHandle, "fragment shader");

	// link it together.
	_programHandle = GL::CreateProgram();
	GL::AttachShader(_programHandle, _vertShaderHandle);
	GL::AttachShader(_programHandle, _pixShaderHandle);
	GL::LinkProgram(_programHandle);
	RZ_GL_PRINTSTATUS(_programHandle, String::Format("program (%s)", shader).GetCharArray());
}

IEffectProperty* GLEffect::CreateAndAddProperty(const String& name)
{
	GLEffectProperty* p = new GLEffectProperty(this, name);
	AddProperty(p);

	return static_cast<IEffectProperty*>(p);
}

u32 GLEffect::FindUniformIndex(const String& name) const
{
	return static_cast<u32>(GL::GetUniformLocation(_programHandle, name.GetCharArray()));
}

void GLEffect::Debug(GLuint object, const char* type)
{
	const GLsizei MAX_LOG_LENGTH = 4096;
	GLchar    log[MAX_LOG_LENGTH];
	GLsizei   logLength = 0;

	if (GL::IsShader(object))
	{
		GL::GetShaderInfoLog(object, MAX_LOG_LENGTH, &logLength, log);
	}
	else
	{
		GL::GetProgramInfoLog(object, MAX_LOG_LENGTH, &logLength, log);
	}

	if (logLength > 0)
	{
		RZ_LOG("[*] Info log of %s:\r\n    %s\r\n    ----------------\r\n", type, log);
	}
}

GLenum GLEffect::GetNextUnitID()
{
	return _unitCounter++;
}

void GLEffect::ResetUnitCounter()
{
	_unitCounter = 0;
}

} } } } }
